package com.partsoft.dits.exts;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.jar.JarFile;

import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import com.partsoft.dits.ConfigurableBean;
import com.partsoft.dits.log.Log;
import com.partsoft.dits.utils.ContextUtils;
import com.partsoft.dits.utils.ReturnExecutionCallback;

@SuppressWarnings("unchecked")
public class BaseExtendsLibraryManager implements ExtendsLibraryManager, ExtendsExecution, ConfigurableBean {
	
	private Executor taskExecutor;
	
	private String extendConfigLocation;
	
	private ExtendsDirectoryClassLoader extendsClassLoader;
	
	private String extendsLocation;
	
	protected ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
	
	private String dependListFileLocation = null;
	
	private boolean overriding = false;
	
	public void setDependListFileLocation(String dependListFileLocation) {
		this.dependListFileLocation = dependListFileLocation;
	}
	
	public String getDependListFileLocation() {
		return dependListFileLocation;
	}
	
	public Executor getTaskExecutor() {
		return taskExecutor;
	}
	
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}
	
	public boolean isOverriding() {
		return overriding;
	}

	public void setOverriding(boolean overriding) {
		this.overriding = overriding;
	}

	public void setTaskExecutor(Executor taskExecutor) {
		this.taskExecutor = taskExecutor;
	}
	
	public boolean isAlreadyInit() {
		return extendsClassLoader != null;
	}
	
	public String getExtendConfigLocation() {
		return extendConfigLocation;
	}

	public void setExtendConfigLocation(String extendConfigLocation) {
		assertAlreadyInit();
		this.extendConfigLocation = extendConfigLocation;
	}

	public ExtendsDirectoryClassLoader getExtendsClassLoader() {
		assertAlreadyInit();
		return extendsClassLoader;
	}
	
	protected void assertAlreadyInit() {
		Assert.isTrue(isAlreadyInit(), "未初始化扩展库上下文。");
	}

	public <T> T executeInExtends(final ReturnExecutionCallback<T> run) {
		assertAlreadyInit();
		ExecutionInExtendsTask<T> task = new ExecutionInExtendsTask<T>(getExtendsClassLoader(), run, getOwnerContext());
		if (taskExecutor == null) {
			task.run();
		} else {
			taskExecutor.execute(task);
		}
		return task.getResult();
	}

	public void finiContext() {
		synchronized (this) {
			if (extendsClassLoader != null) {
				extendsClassLoader.finiContext();
			}
			extendsClassLoader = null;
		}
	}
	
	protected ClassLoader resolveParentClassLoader() {
		return this.getClass().getClassLoader();
	}
	
	public void initContext() {
		synchronized (this) {
			Assert.notNull(getExtendsLocation(), "请设置扩展库位置。");
			if (extendsClassLoader != null) {
				finiContext();
			}
			try {
				extendsClassLoader = new ExtendsDirectoryClassLoader(resolveParentClassLoader());
				extendsClassLoader.setLibrariesLocation(getExtendsLocation());
				extendsClassLoader.setExtendConfigLocation(getExtendConfigLocation());
				extendsClassLoader.setOverriding(isOverriding());
				extendsClassLoader.initContext();
			} catch (Exception e) {
				extendsClassLoader = null;
				throw new IllegalArgumentException(e.getMessage(), e);
			}
		}
	}

	public String getExtendsLocation() {
		return extendsLocation;
	}

	/**
	 * 设置扩展库路径
	 * @param extendsLibDirectory 可以是类路径(classpath:)或者本地文件路径(file:/)以及网络路径，默认为类路径。
	 */
	public void setExtendsLocation(String extendsLibDirectory) {
		this.extendsLocation = extendsLibDirectory;
	}
	
	public Set<String> getExtendsAliasNamespaces() {
		Assert.isInstanceOf(ExtendsDirectoryClassLoader.class, Thread.currentThread().getContextClassLoader() , "此方法必须运行在扩展类装载器中。");
		return Collections.unmodifiableSet(new HashSet<String>(getExtendsClassLoader().getExtendsAliasNamespaces()));
	}

	public <T> Map<String, T> getExtendsAliasObjects(String namespace) {
		Assert.isInstanceOf(ExtendsDirectoryClassLoader.class, Thread.currentThread().getContextClassLoader() , "此方法必须运行在扩展类装载器中。");
		
		namespace = StringUtils.hasText(namespace) ? namespace.trim() : "";
		Map<String, Map<String, String>> aliasMaps = getExtendsClassLoader().getExtendsAliasMaps(); 

		if (!aliasMaps.containsKey(namespace))
			return Collections.EMPTY_MAP;
		Map<String, String> aliasMap = aliasMaps.get(namespace);
		if (aliasMap.size() == 0)
			return Collections.EMPTY_MAP;
		
		Map<String, Object> returnMap = new LinkedHashMap<String, Object>();
		for (Map.Entry<String, String> aliasEntry : aliasMap.entrySet()) {
			String tempAliasName = aliasEntry.getKey();
			String clazzName = aliasEntry.getValue();
			try {
				Class<?> clazz = ClassUtils.forName(clazzName, Thread.currentThread().getContextClassLoader());
				Constructor<?> tempCons = ClassUtils.getConstructorIfAvailable(clazz, new Class<?>[0]);
				Object tempBo = tempCons.newInstance(new Object[0]);
				returnMap.put(tempAliasName, tempBo);
			} catch (Throwable e) {
				Log.fatal(String.format("别名(%s.%s)定义的类(%s)找不到，可能已经被清除。", namespace, tempAliasName, clazzName), e);
			}
		}
		return (Map<String, T>) Collections.unmodifiableMap(new HashMap<String, Object>(returnMap));
	}

	public boolean hasExtendsPackage(String jarName) {
		if (jarName == null) return false;
		jarName = jarName.toLowerCase();
		return getExtendsClassLoader().getExtendsPackageNames().contains(jarName);
	}

	public Set<ExtendsLibraryInfomation> getExtendsLibraryInformations() {
		return getExtendsClassLoader().getExtendsLibraryInformations();
	}
	
	/**
	 * 如果删除尝试不成功，则进行{@link System#gc()}后重试（60次）。<br/>
	 */
	public boolean removeExtendsPackage(String jarName) {
		if (!hasExtendsPackage(jarName)) return false;
		File file = null;
		try {
			file = getExtendsClassLoader().removeExtendsClassPath(jarName);
		} catch (IOException e) {
			Log.error(String.format("移除扩展包(%s)时发生错误: %s", jarName, e.getMessage()), e);
		}
		int delCount = 1;
		boolean result = false;
		System.gc();
		while ((!(result = FileUtils.deleteQuietly(file))) && delCount < 60) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				break;
			}
			delCount ++;
			System.gc();
		}
		//成功装载jar
		try {
			getExtendsClassLoader().loadJars();
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
		return result;
	}

	public void saveExtendsPackage(String packageName, byte[] fileBytes) throws IOException {
		Assert.notNull(packageName);
		Assert.isTrue(fileBytes != null, "无文件内容");
		assertAlreadyInit();
		
		packageName = packageName.toLowerCase();
		
		//检查是否JAR或ZIP包
		File file =new File(System.getProperty("java.io.tmpdir"));
		if (!(file.exists() && file.isDirectory()))
			throw new IOException(String.format("临时目录(%s)不存在？", file.getPath()));
		File tmpFile = new File(file, packageName+ new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()));
		try {
			FileUtils.writeByteArrayToFile(tmpFile, fileBytes);
			new JarFile(tmpFile);
		} catch (Throwable e) {
			throw new IOException(String.format("文件(%s)不是有效的压缩文件", packageName), e);
		} finally {
			tmpFile.delete();
		}
		
		//保存
		File extDir = getExtendsClassLoader().getLibrariesDirectory();
		packageName = packageName.toLowerCase();
		if (!(packageName.endsWith(".jar") || packageName.endsWith(".zip")))
			packageName = packageName + ".jar";
		File pFile = new File(extDir, packageName);
		FileUtils.writeByteArrayToFile(pFile, fileBytes);
		getExtendsClassLoader().loadJars();
	}
	
	private List<String> getCoreResourceLines(String classpath) throws IOException {
		Assert.hasText(classpath);
		URL resURL = this.getClass().getResource(classpath);
		if (resURL == null) throw new IOException("Resource not found!");
		InputStream resm = null;
		try{
			resm = resURL.openStream();
			return IOUtils.readLines(resm);
		} finally {
			IOUtils.closeQuietly(resm);
		}
	}
	
	public String[] getJarNamesOfDepends() {
		List<String> jarNames = null;
		if (StringUtils.hasText(this.dependListFileLocation)) {
			Resource fs = resourceLoader.getResource(this.dependListFileLocation);
			if (fs != null && fs.exists()) {
				InputStream resm = null;
				try{
					resm = fs.getInputStream();
					jarNames = IOUtils.readLines(resm);
				} catch (IOException e) {} finally {
					IOUtils.closeQuietly(resm);
				}
			}
		}
		if (jarNames == null) {
			try {
				jarNames = getCoreResourceLines("depends.list");
			} catch (IOException e) {}
		}
		return jarNames != null ? jarNames.toArray(new String[0]) : new String[0];
	}
	
	protected ApplicationContext getOwnerContext() {
		return null;
	}
	
	public static class ExecutionInExtendsTask<T> implements Runnable {
		
		private T result;
		
		private final ReturnExecutionCallback<T> callback;
		
		private final ClassLoader classLoader;
		
		private final ApplicationContext ctx;
		
		private Semaphore semObject = new Semaphore(1);
		
		public ExecutionInExtendsTask(ClassLoader classLoader, ReturnExecutionCallback<T> callback, ApplicationContext ctx){
			Assert.notNull(classLoader);
			Assert.notNull(callback);
			this.classLoader = classLoader;
			this.callback = callback;
			this.ctx = ctx;
		}
		
		public T getResult() {
			try {
				semObject.acquire();
			} catch (InterruptedException e) {} finally {
				semObject.release();
			}
			return result;
		}

		public void run() {
			ClassLoader ocld = Thread.currentThread().getContextClassLoader();
			ApplicationContext oldAppCtx = null;
			ThreadLocal<ApplicationContext> tlc = null;
			
			try {
				semObject.acquire();
				if (this.ctx != null) {
					Field clf = null;
					try {
						clf = ContextUtils.class.getDeclaredField("contextLocal");
					} catch (Exception e) {
						throw new IllegalStateException(e.getMessage(), e);
					}
					boolean oldAcs = clf.isAccessible();
					clf.setAccessible(true);
					tlc = (ThreadLocal<ApplicationContext>) clf.get(null);
					if (tlc != null) {
						oldAppCtx = tlc.get();
						tlc.set(ctx);
					}
					clf.setAccessible(oldAcs);
				}
				Thread.currentThread().setContextClassLoader(classLoader);
				result = callback.callback();
			} catch (Throwable e) {
				throw new IllegalStateException("执行错误:", e);
			} finally {
				if (this.ctx != null && tlc != null) tlc.set(oldAppCtx);
				Thread.currentThread().setContextClassLoader(ocld);
				semObject.release();
			}
		}
		
	}
	
}
